using System;
using System.ServiceModel;

// KNOWN: MS screwed the pooch when implementing IDisposable
// http://www.codeproject.com/KB/dotnet/idisposable.aspx

// UNKNOWN: and it is coming back to bite us again in WCF.
// http://www.danrigsby.com/blog/index.php/2008/02/26/dont-wrap-wcf-service-hosts-or-clients-in-a-using-statement/

// after pulling my hair out for a couple days dealing with obtuse communication exceptions I 
// remembered KNOWN and found UNKNOWN and implemented a helper class using anonymous methods (Action) which worked
// fairly well but needed some refactoring.

// So I went looking for some background on ChannelFactory to refactor(y) my channel creation/cleanup strategy i found 
// an interesting approach on this page
// http://nimtug.org/blogs/damien-mcgivern/archive/2009/05/26/wcf-communicationobjectfaultedexception-quot-cannot-be-used-for-communication-because-it-is-in-the-faulted-state-quot-messagesecurityexception-quot-an-error-occurred-when-verifying-security-for-the-message-quot.aspx
//
// it is written to use against generated proxy classes but i could see it would be well used with a ChannelFactory/Channel 
// strategy.  In the middle of my implementation I had a  question on how to determine the state of the actual channel from 
// context as CreateChannel returns a transparent proxy class and guess what I found in the user content of 
// http://msdn.microsoft.com/en-us/library/ms734681.aspx ?
//
// EXACTLY the changes that i was in the middle of implementing! And done well at that. saved some time. Thanks dude.

// hmmm.. they are so similar, i wonder if one is derived from the other. small world.

// The existin logic was sound and I needed only to add an overload to allow arbitrary endpoints.

namespace Salient.ServiceModel
{
    /// <summary>
    /// This class contains utility methods related to invoking WCF services.
    /// Source: wiki content on MSDN - http://msdn.microsoft.com/en-us/library/ms734681.aspx
    /// 
    /// If you enhance or uncover bugs, go over there and update the code.
    /// </summary>
    public static class ServiceInvoker
    {
        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingProxy<TService>(Action<TService> action)
            where TService : class, ICommunicationObject, IDisposable, new()
        {
            // create an instance of TService and invoke the action
            var service = new TService();
            service.InvokeAction(action);
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// 
        /// This method will use the first matching endpoint found in configuration.
        /// To specify an endpoint use the appropriate overload.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Action<TService> action)
            where TService : class
        {
            UsingFactory(action, "*");
        }

        /// <summary>
        /// Alternative to the using statement to handle exceptions thrown by the Close method
        /// by calling the Abort method to ensure the transition to the Closed state.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="endpointName">Client endpoint name.</param>
        /// <typeparam name="TService">The service type.</typeparam>
        public static void UsingFactory<TService>(Action<TService> action, string endpointName) where TService : class
        {
            // TODO: cache the channel factory of TService
            var factory = new ChannelFactory<TService>(endpointName);
            // create an instance of TService and invoke the action
            TService service = factory.CreateChannel();
            service.InvokeAction(action);
        }


        /// <summary>
        /// Invokes an action on a service then disposes the service channel.
        /// </summary>
        /// <typeparam name="TService">The service type.</typeparam>
        /// <param name="service">The service.</param>
        /// <param name="action">The action.</param>
        private static void InvokeAction<TService>(this TService service, Action<TService> action)
            where TService : class
        {
            try
            {
                // invoke action with service as its parameter
                action(service);
            }
            catch (Exception)
            {
                // todo: add logging here
                throw;
            }
            finally
            {
                // always close or abort the service channel
                ((ICommunicationObject) service).CloseOrAbort();
            }
        }
    }
}